/***************************************************************** 
	文件名：mem.c 
	abstract :
				把一个给定的虚地址转换为内存的物理地址

	输入参数： 
				pid 接收待查询进程的PID 
				va 接收待查询的虚拟地址 
*****************************************************************/ 
#include  <linux/module.h> 
#include <linux/kernel.h> 
#include <linux/init.h> 
#include <linux/sched.h> 
#include <linux/mm.h> 
#include <asm/pgtable.h> 
#include <asm/page.h> 

unsigned long vaddr = 0; 

static void get_pgtable_macro(void)   
{       
	printk("PAGE_OFFSET = x%lx\n", PAGE_OFFSET);       
	printk("PGDIR_SHIFT = %d\n", PGDIR_SHIFT);       
	printk("PUD_SHIFT = %d\n", PUD_SHIFT);       
	printk("PMD_SHIFT = %d\n", PMD_SHIFT);       
	printk("PAGE_SHIFT = %d\n", PAGE_SHIFT);           
	printk("PTRS_PER_PGD = %d\n", PTRS_PER_PGD);       
	printk("PTRS_PER_PUD = %d\n", PTRS_PER_PUD);       
	printk("PTRS_PER_PMD = %d\n", PTRS_PER_PMD);       
	printk("PTRS_PER_PTE = %d\n", PTRS_PER_PTE);           
	printk("PAGE_MASK = x%lx\n", PAGE_MASK);   
}       

static unsigned long vaddrpaddr(unsigned long vaddr)   
	{       
	pgd_t *pgd;       
	pud_t *pud;       
	pmd_t *pmd;       
	pte_t *pte;       
	unsigned long paddr = 0;           
	unsigned long page_addr = 0;      
	unsigned long page_offset = 0;           
	
	pgd = pgd_offset(current->mm, vaddr);      
	printk("pgd_val = x%lx\n", pgd_val(*pgd));       
	printk("pgd_index = %lu\n", pgd_index(vaddr));      
	if (pgd_none(*pgd)) {           
	printk("not mapped in pgd\n");           
	return -1;       
	}           
	
	pud = pud_offset(pgd, vaddr);       
	printk("pud_val = x%lx\n", pud_val(*pud));       
	if (pud_none(*pud)) {           
		printk("not mapped in pud\n");           
		return -1;       
	}           
		
	pmd = pmd_offset(pud, vaddr);       
	printk("pmd_val = x%lx\n", pmd_val(*pmd));      
	printk("pmd_index = %lu\n", pmd_index(vaddr));       
	if (pmd_none(*pmd)) {           
		printk("not mapped in pmd\n");           
		return -1;       
	}
		
	pte = pte_offset_kernel(pmd, vaddr);       
	printk("pte_val = x%lx\n", pte_val(*pte));       
	printk("pte_index = %lu\n", pte_index(vaddr));      
	if (pte_none(*pte)) {           
		printk("not mapped in pte\n");           
		return -1;       
	}           
		
	//页框物理地址机制 | 偏移量       
	page_addr = pte_val(*pte) & PAGE_MASK;       
	page_offset = vaddr & ~PAGE_MASK;       
	paddr = page_addr | page_offset;       
	printk("page_addr = %lx, page_offset = %lx\n", page_addr, page_offset);           
	printk("vaddr = %lx, paddr = %lx\n", vaddr, paddr);           
	return paddr;   
}       
		
static int __init v2p_init(void)   
{                 
	printk("vaddr to paddr module is running..\n");    
	
	get_pgtable_macro();       
	printk("\n");        
	
	vaddr = (unsigned long)vmalloc( 1000 * sizeof(char));       
	if (vaddr == 0) {           
		printk("vmalloc failed..\n");           
		return ;       
	}       

	printk("vmalloc_vaddr=x%lx\n", vaddr);       
	vaddrpaddr(vaddr);           
	printk("\n\n");       

	vaddr = __get_free_page(GFP_KERNEL);   
	
	if (vaddr == 0) {           
		printk("__get_free_page failed..\n");           
		return 0;       
	}       
	
	printk("get_page_vaddr=x%lx\n", vaddr);       
	vaddrpaddr(vaddr);           
	
	return 0;   
}       
		
static void __exit v2p_exit(void)   
{       
	printk("vaddr to paddr module is leaving..\n");           
	vfree((void *)vaddr);           
	free_page(vaddr);   
} 


module_init(v2p_init); 
module_exit(v2p_exit);

MODULE_LICENSE("GPL");


/*

整个程序的结构如下：

1.get_pgtable_macro()打印当前系统分页机制中的一些宏。

2.通过vmalloc()在内核空间中分配内存，调用vaddr2paddr()将虚拟地址转化成物理地址。

3.通过__get_free_pages()在内核空间中分配页框，调用vaddr2paddr()将虚拟地址转化成物理地址。

4.分别通过vfree()和free_page()释放申请的内存空间。

vaddr2paddr()的执行过程如下：

1.通过pgd_offset计算页全局目录项的线性地址pgd，传入的参数为内存描述符mm和线性地址vaddr。接着打印pgd所指的页全局目录项。

2.通过pud_offset计算页上级目录项的线性地址pud，传入的参数为页全局目录项的线性地址pgd和线性地址vaddr。接着打印pud所指的页上级目录项。

3.通过pmd_offset计算页中间目录项的线性地址pmd，传入的参数为页上级目录项的线性地址pud和线性地址vaddr。接着打印pmd所指的页中间目录项。

4.通过pte_offset_kernel计算页表项的线性地址pte，传入的参数为页中间目录项的线性地址pmd和线性地址vaddr。接着打印pte所指的页表项。

5.pte_val(*pte)先取出页表项，与PAGE_MASK相与的结果是得到要访问页的物理地址；vaddr&~PAGE_MASK用来得到线性地址offset字段；两者或运算得到最终的物理地址。

6.打印物理地址。

*/
